Skip to content

[@mantine/core] FloatingIndicator: Fix position and size calculation under scaled ancestors#9071

Open
saadpocalypse wants to merge 1 commit into
mantinedev:masterfrom
saadpocalypse:fix/floating-indicator-scaled-ancestors
Open

[@mantine/core] FloatingIndicator: Fix position and size calculation under scaled ancestors#9071
saadpocalypse wants to merge 1 commit into
mantinedev:masterfrom
saadpocalypse:fix/floating-indicator-scaled-ancestors

Conversation

@saadpocalypse

Copy link
Copy Markdown
Contributor

Problem

FloatingIndicator uses target.getBoundingClientRect() and parent.getBoundingClientRect() to compute dimensions and position offsets.

getBoundingClientRect() returns values in viewport coordinate space, which incorporates any CSS scale transforms (transform: scale(...)) applied to ancestor elements or during enter/exit animations (e.g. Modal transitions).

When inline CSS width, height, and transform values are assigned using these viewport-space measurements on an element inside a scaled ancestor:

  • The ancestor's scale factor is applied a second time to the indicator.
  • The floating indicator (and components using it internally, such as SegmentedControl) renders undersized (e.g., ~90% of the target's width) and shifted toward the transform origin.
  • ResizeObserver does not correct the indicator after transition completion because CSS transforms do not alter unscaled content-box layout geometry.

Solution

Normalized viewport measurements back into the parent's local coordinate system in packages/@mantine/core/src/components/FloatingIndicator/use-floating-indicator.ts:

  • Computed horizontal (scaleX) and vertical (scaleY) scale factors by comparing parent.getBoundingClientRect() against parent.offsetWidth and parent.offsetHeight.
  • Guarded against zero dimensions (offsetWidth === 0 or offsetHeight === 0) to safely fallback to 1 when elements are hidden or unmounted.
  • Normalized top, left, width, and height by dividing viewport-space deltas and dimensions by scaleY and scaleX.
  • Added a new story ScaledAncestor to packages/@mantine/core/src/components/SegmentedControl/SegmentedControl.story.tsx to visually test indicator geometry under scaled ancestors.

Tests

  • Automated Tests: Added a unit test in packages/@mantine/core/src/components/FloatingIndicator/FloatingIndicator.test.tsx asserting correct normalization when parent bounding geometry is scaled. Verified that all 316 @mantine/core Jest test suites (9,725 tests) pass (npm run jest @mantine/core).
  • Build, Typecheck & Formatting: Successfully verified that the codebase typechecks (npm run typecheck), builds without errors (npm run build), passes linting (npx oxlint), and follows repository formatting (oxfmt).
  • Manual Verification: Verified visually using the new ScaledAncestor story in Storybook (SegmentedControl > ScaledAncestor).

Resolves #9070

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FloatingIndicator uses transformed viewport geometry as local CSS coordinates

1 participant